AlaskaLinuxUser's Scratchpad

Commit thy works unto the LORD, and thy thoughts shall be established. - Proverbs 16:3

picoEngine gets an update

20251126.jpg

If you've been around my blog for a long while, you may remember my old chess engine I made back in 2019. It was called picoEngine [1] and was written in C++. It wasn't a very good engine, to be honest, mostly making weighted moves, but could handle searching for several ply. Ply is chess engine talk for turns. Anyways, there was an old, outstanding issue on the gitlab repository for picoEngine, where a user said that the engine sometimes disconnected instead of making a move.

Originally, I stress tested picoEngine by running countless games in Arena, a chess program that allowed engine vs engine, and the results seem to be satisfactory. However, after rebuilding picoEngine on my local machine, and running it against PyChess.py in Pychess, it would often not finish a game and disconnect.

After a little troubleshooting, I realized that I should add a try/catch block to make sure that if it fails to calculate a move, it doesn't quit and returns something, even if it is not a good move. My edit looks like this:

void inputIsReady()
    cout << "Going...." << endl;
    calculate = true;
    engineMove = "";
    // Get a random move before calculating, so if the engine runs out of time or fails to calculate, it will return something rather than nothing.
    engineMove = movePick.bestMove(board.moveBoard(), board.getTurn(), true, board.getIsPassent(), board.getEnPassent(), chosenPly);

    try {         
            // Try to calculate a move.
            engineMove = movePick.bestMove(board.moveBoard(), board.getTurn(), styleRandom, board.getIsPassent(), board.getEnPassent(), chosenPly);
    } 
    catch (...) {   
            // If you can't calculate a move, pick a random one.
            engineMove = movePick.bestMove(board.moveBoard(), board.getTurn(), true, board.getIsPassent(), board.getEnPassent(), chosenPly);
    }

    cout << "bestmove " + engineMove << endl;

I know this is pretty bad, but it was a quick fix that did the trick. The worst part is just returning a random move if the calculations fail for any reason. Doesn't help picoEngine's play strength, but does make it more reliable!

I also took a few minutes to watch it play 30 games against PyChess.py, at PyChess's lowest setting, and picoEngine's best setting. I noticed that it made very strange moves, things that are not only blunders or mistakes, but just not bothering to take an enemy piece when it could and had no reason not to. It was also missing mate in one, and just watching it play was very unusual to see.

I decided to look over the evaluation, and made several edits. Namely, I adjusted the piece value a bit, lessened the value of mobility (the addition of eval points because a piece has more move squares), and adjusted some of the piece tables, like the pawnBoard:

int pawnBoard[]=
                    {20, 20,  20,  20,  20,  20,  20,  20,
                    10,  10,  10,  10,  10,  10,  10,  10,
                    8,  8,  8,  8,  8,  8,  8,  8,
                    6,  6,  6,  6,  6,  6,  6,  6,
                    3,  0,  1,  4,  4,  0,  0,  3,
                    2,  2,  1,  3,  3,  1,  2,  2,
                    1,  1,  1, -1, -1,  1,  1,  1,
                    0,  0,  0,  0,  0,  0,  0,  0};

This adjustment seemed to help tremendously. While it didn't make picoEngine a solid player, the moves started to look more natural. Things like pushing pawns at the end of the game started to happen, and taking back pieces that captured one of yours became the rule and not the exception. I don't know a good way to evaluate picoEngine's newfound strength, but in 9 game rounds against PyChess.py (on PyChess's lowest setting), it actually started winning some games! Some, but not many. Keep in minde that PyChess uses an opening book for both engines. Here is one of those games:

[Event "Local Event"]
[Site "Local Site"]
[Date "2025.11.26"]
[Round "1"]
[White "PyChess.py"]
[Black "picoEngine 1.2"]
[Result "0-1"]
[PlyCount "80"]
[Termination "normal"]
[WhiteClock "0:04:44.797"]
[BlackClock "0:04:39.444"]
[TimeControl "300+0"]
[ECO "B42"]
[Opening "Sicilian Defense"]
[Variation "Kan Variation, Modern Variation"]

1. e4 c5 2. Nf3 e6 3. d4 cxd4 4. Nxd4 a6 5. Bd3 b5 6. O-O Bb7 7. Be3 b4 8. Re1
Nf6 9. Nd2 e5 10. Nf5 Nc6 11. Rc1 g6 12. Ng3 a5 13. Nc4 a4 14. Be2 d5 15. exd5
Ne7 16. d6 Nc6 17. Kh1 h5 18. Kg1 h4 19. Nf1 e4 20. Qd2 Qd7 21. Rcd1 Bg7 22. Kh1
Qf5 23. Kg1 O-O 24. Kh1 a3 25. Nb6 Rae8 26. d7 Nxd7 27. Qxd7 Qxd7 28. Rxd7 axb2
29. Rxb7 Be5 30. Bg4 Rd8 31. Bg5 Rd4 32. Bxh4 Rd6 33. Rxe4 Bc3 34. Nd7 Ra8 35.
Bg3 Rd5 36. Ne3 Rd2 37. Rc7 b1=Q+ 38. Bd1 Rxd1+ 39. Nxd1 Qxd1+ 40. Re1 Qxe1# 0-1

Pretty neat to see a victory, especially one where picoEngine promoted a pawn to a queen! (I'm so proud of you, little picoEngine!) Keep in mind that my goal with picoEngine is to keep it small and lightweight to use. The entire source code, not counting the build script is only 138.1 KB, and the binary, build on my machine is only 394 KB, meaning it could fit multiple times on an old floppy drive diskette!

Here is a table showing the results from my 5 test runs of 9 games against PyChess.py at the lowest setting and using an opening book:

Run Wins Draw Loss Score
1 1 2 6 2.0
2 1 0 8 1.0
3 1 1 7 1.5
4 0 2 7 1.0
5 0 3 6 1.5

I probably should do a few tests without an opening book, as that would be the most thorough test. However, it is painful (in my opinion) to watch chess engines without an opening book. For one, it seems to take most of them a while to get to the action, and the games just don't look interesting to me.

Linux - keep it simple.

[1] picoEngine